/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.XPath;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	public sealed class Topic
	{
		#region Public Properties
		public string Id
		{
			get
			{
				return GetAttribute(element, "id");
			}
			private set
			{
				SetAttribute(element, "id", value);
			}
		}

		public string File
		{
			get
			{
				return GetFile(element);
			}
			private set
			{
				SetFile(element, value);
			}
		}

		public string TocTitle
		{
			get
			{
				return (metadata == null) ? GetTitle(element) : metadata.TocTitle;
			}
			set
			{
				if (metadata == null)
					SetTitle(element, value);
				else
					metadata.TocTitle = value;
			}
		}

		public TopicMetadata Metadata
		{
			get
			{
				return metadata;
			}
			set
			{
				metadata = value;
			}
		}

		public bool IsApiPlaceholder
		{
			get
			{
				return string.Equals(element.Name, TopicsDocument.ApiRootPlaceholderElementName, StringComparison.Ordinal);
			}
		}

		public bool Expose
		{
			get
			{
				if (topicType != TopicType.Conceptual && topicType != TopicType.Container && topicType != TopicType.Html)
					return shouldExpose;
				else
				{
					string value = GetAttribute(element, "expose");
					bool exposed;

					return string.IsNullOrEmpty(value) || !bool.TryParse(value, out exposed) || exposed;
				}
			}
			set
			{
				if (topicType != TopicType.Conceptual && topicType != TopicType.Container && topicType != TopicType.Html)
					shouldExpose = value;
				else if (value)
					// remove attribute since the default value is true
					SetAttribute(element, "expose", null);
				else
					SetAttribute(element, "expose", value.ToString());
			}
		}

		public string Assembly
		{
			get
			{
				return (apiElement == null) ? string.Empty : apiElement.AssemblyName;
			}
		}

		public string Name
		{
			get
			{
				return (apiElement == null) ? string.Empty : apiElement.TopicName;
			}
		}

		public ReflectionApiElement ApiElement
		{
			get
			{
				return apiElement;
			}
		}

		public TopicType TopicType
		{
			get
			{
				return topicType;
			}
		}

		public TopicAccessibility TopicAccess
		{
			get
			{
				return (apiElement == null) ? TopicAccessibility.Public : apiElement.TopicAccess;
			}
		}

		public bool IsType
		{
			get
			{
				return ReflectionApiElement.IsType(topicType);
			}
		}

		public bool IsMember
		{
			get
			{
				return ReflectionApiElement.IsMember(topicType);
			}
		}

		public bool IsMethodOverloads
		{
			get
			{
				return (apiElement == null) ? false : apiElement.IsMethodOverloads;
			}
		}

		public bool HasMethodOverloads
		{
			get
			{
				return (apiElement == null) ? false : apiElement.HasMethodOverloads;
			}
		}

		public ICollection<string> MethodParameters
		{
			get
			{
				return methodParameters.AsReadOnly();
			}
		}

		public ICollection<string> TypeParameters
		{
			get
			{
				return typeParameters.AsReadOnly();
			}
		}

		public Topic Parent
		{
			get
			{
				return parent;
			}
			internal set
			{
				parent = value;
			}
		}

		public Topic ParentNamespace
		{
			get
			{
				Topic currentParent = null, topic = this;

				while ((currentParent = topic.Parent) != null)
				{
					if (currentParent.TopicType == TopicType.Namespace)
						return currentParent;

					topic = currentParent;
				}

				return null;
			}
		}

		public Topic ParentType
		{
			get
			{
				Topic currentParent = null, topic = this;

				while ((currentParent = topic.Parent) != null)
				{
					if (currentParent.IsType)
						return currentParent;

					topic = currentParent;
				}

				return null;
			}
		}

		public IXPathNavigable Element
		{
			get
			{
				return element;
			}
		}
		#endregion

		#region Private / Protected
		/// <summary>
		/// Used by the <see cref="TopicManager" /> class when applying changes to keep track of whether a topic is exposed.
		/// </summary>
		internal bool ShouldExpose
		{
			get
			{
				return shouldExpose;
			}
			set
			{
				shouldExpose = value;
			}
		}

		internal bool ChildrenLoaded
		{
			get
			{
				return childrenLoaded;
			}
			set
			{
				childrenLoaded = value;
			}
		}

		private bool shouldExpose;
		private bool childrenLoaded;
		private Topic parent;
		private TopicMetadata metadata;

		private readonly TopicType topicType;
		private readonly List<string> methodParameters, typeParameters;
		private readonly ReflectionApiElement apiElement;
		private readonly XPathNavigator element;
		#endregion

		#region Constructors
		private Topic(IXPathNavigable element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			this.element = element.CreateNavigator();
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="Topic" /> class.
		/// </summary>
		private Topic(IXPathNavigable element, string id)
			: this(element)
		{
			if (string.IsNullOrEmpty(id))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "id");

			Id = id;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="Topic" /> class.
		/// </summary>
		internal Topic(TopicType topicType, IXPathNavigable element, string id, string title)
			: this(element, id)
		{
			TocTitle = title;

			this.topicType = topicType;

			methodParameters = new List<string>(0);
			typeParameters = new List<string>(0);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="Topic" /> class.
		/// </summary>
		internal Topic(TopicType topicType, IXPathNavigable element, string id, string title, string file)
			: this(topicType, element, id, title)
		{
			File = file;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="Topic" /> class.
		/// </summary>
		internal Topic(ReflectionApiElement apiElement, IXPathNavigable element, string id)
			: this(element, id)
		{
			if (apiElement == null)
				throw new ArgumentNullException("apiElement");

			this.apiElement = apiElement;

			topicType = apiElement.TopicType;

			methodParameters = new List<string>(apiElement.MethodParameters);
			typeParameters = new List<string>(apiElement.TypeParameters);

			TocTitle = apiElement.Title;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="Topic" /> class by copying the values of the specified <paramref name="clone"/>.
		/// </summary>
		public Topic(Topic clone, IXPathNavigable element)
			: this(clone)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			this.element = element.CreateNavigator();
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="Topic" /> class by copying the values of the specified <paramref name="clone"/>.
		/// </summary>
		public Topic(Topic clone)
		{
			if (clone == null)
				throw new ArgumentNullException("clone");

			if (clone.apiElement != null)
				apiElement = new ReflectionApiElement(clone.apiElement);

			topicType = clone.topicType;
			element = clone.element;
			shouldExpose = clone.shouldExpose;
			childrenLoaded = clone.childrenLoaded;
			methodParameters = new List<string>(clone.methodParameters);
			typeParameters = new List<string>(clone.typeParameters);
		}
		#endregion

		#region Methods
		public static string GetFile(IXPathNavigable element)
		{
			return GetAttribute(element, "file");
		}

		public static void SetFile(IXPathNavigable element, string file)
		{
			SetAttribute(element, "file", file);
		}

		public static string GetTitle(IXPathNavigable element)
		{
			return GetAttribute(element, "title");
		}

		public static void SetTitle(IXPathNavigable element, string title)
		{
			SetAttribute(element, "title", title);
		}

		private static string GetAttribute(IXPathNavigable element, string name)
		{
			XPathNavigator navigator = element.CreateNavigator();

			return navigator.GetAttribute(name, "");
		}

		private static void SetAttribute(IXPathNavigable element, string name, string value)
		{
			XPathNavigator navigator = element.CreateNavigator();

			if (navigator.MoveToAttribute(name, ""))
			{
				if (value == null)
					navigator.DeleteSelf();
				else
					navigator.SetValue(value);
			}
			else if (value != null)
				navigator.CreateAttribute(null, name, null, value);
		}

		public override string ToString()
		{
			return TocTitle + " {" + Id + "}";
		}
		#endregion
	}
}
